import platform 
import ctypes
import sys
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from agh_control import Simulation
from chart_widget import ChartWidget
from toolbox import *
from graph_widget import GraphWidget
from titlebar import TitleBar
from menubar import MenuBar
from toolbar import ToolBar

WINDOW_MIN_SIZE = QSize(1280, 720)
BASIC_TIMER_INTERVAL = 20
WINDOW_TITLE = "机场地勤车辆调度模拟仿真系统"

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.timer = QTimer()
        self.play_state = False
        self.new_start = True
        self.is_change_dispath_method = False
        self.sim = Simulation()
        self.content_index = 1
        self.data_loading_thread = DataLoadingThread()
        self.data_saving_thread = DataSavingThread()
        self.model_tip_dialog = ModalTipDialog()
        self.tip_dialog = TipDialog()
        self.ga_parms_dialog = GAParmsDialog()
        self.data_loaded_callback = None
        
        self.init_ui()
        self.init_connection()
        self.init_window()

    def init_ui(self):
        self.chart_widget = ChartWidget()
        self.graph_widget = GraphWidget()

        layout = QStackedLayout()
        layout.addWidget(self.chart_widget)
        layout.addWidget(self.graph_widget)
        layout.setCurrentIndex(1)
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.stacked_layout = layout

        self.title_bar = TitleBar(self, WINDOW_TITLE)
        self.tool_bar = ToolBar(self)
        self.menu_bar = MenuBar(self)
        self.tool_bar.dispatching_comboBox.addItems(self.sim.get_dispatch_method_names())

        #指定focus在标题栏，以免默认focus在开始按钮，导致有点不好看
        self.title_bar.setFocus()
        
        layout = QVBoxLayout()
        layout.addWidget(self.title_bar)
        layout.addWidget(self.menu_bar)
        layout.addWidget(self.tool_bar)
        layout.addLayout(self.stacked_layout )
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

    def init_window(self):
        self.setWindowTitle(WINDOW_TITLE)
        self.setObjectName("MainWindow")
        self.setMinimumSize(WINDOW_MIN_SIZE)
        self.setContentsMargins(0, 0, 0, 0)
        self.setWindowFlag(Qt.WindowType.FramelessWindowHint)
        self.move(int((self.screen().geometry().width() - self.width())/2), int((self.screen().geometry().height() - self.height())/2))

    def init_connection(self):
        self.timer.timeout.connect(self.update_content)
        
        self.menu_bar.open_vehicle_conf_action.triggered.connect(self.open_vehicle_conf)
        self.menu_bar.open_flight_data_file_action.triggered.connect(self.open_flight_data_file)
        self.menu_bar.open_gate_data_file_action.triggered.connect(self.open_gate_data_file)
        self.menu_bar.save_chart_action.triggered.connect(self.save_chart_image)
        self.menu_bar.save_dipath_plan_action.triggered.connect(self.save_dipath_plan)
        self.menu_bar.chart_view_action.triggered.connect(lambda:self.switch_showing_content(0))
        self.menu_bar.graph_view_action.triggered.connect(lambda:self.switch_showing_content(1))
        self.menu_bar.show_info_action.triggered.connect(self.switch_info_layer_state)
        self.menu_bar.show_cost_figure_action.triggered.connect(self.show_cost_figure)
        self.menu_bar.ga_parms_action.triggered.connect(self.show_ga_parms_dialog)

        self.tool_bar.play_button.clicked.connect(self.switch_play_state)
        self.tool_bar.restart_button.clicked.connect(self.restart)
        self.tool_bar.speed_slider.valueChanged.connect(lambda value:self.timer.setInterval(int(BASIC_TIMER_INTERVAL / value)))
        self.tool_bar.dispatching_comboBox.currentIndexChanged.connect(self.switch_dispath_method)

        self.data_loading_thread.loading_complete_signal.connect(self.update_widgets_after_loaded_data)
        self.data_saving_thread.finished.connect(lambda:self.model_tip_dialog.hide())

        self.ga_parms_dialog.changed_parms_signal.connect(self.changed_ga_parms)

    def switch_showing_content(self, index):
        self.stacked_layout.setCurrentIndex(index)
        self.content_index = index
        if self.content_index == 0:
            self.update_chart_content()
            self.menu_bar.show_info_action.setDisabled(True)
        else:
            self.update_graph_content()
            self.menu_bar.show_info_action.setDisabled(False)

    def switch_play_state(self):
        self.play_state = not self.play_state
        if self.play_state:
            self.start()
        else:
            self.pause()
    
    def switch_info_layer_state(self):
        self.graph_widget.info_layer.setVisible(not self.graph_widget.info_layer.isVisible())
    
    def switch_dispath_method(self, index:int):
        self.set_new_start(is_change_dispath_method = True)
        self.sim.set_dispatch_method(index)
    
    def set_new_start(self, is_change_dispath_method:bool = False):
        self.new_start = True
        self.play_state = False
        self.is_change_dispath_method = is_change_dispath_method
        self.chart_widget.clear()
        self.graph_widget.clear()
        self.pause()

    def start(self):
        if self.new_start:
            can_start, message = self.sim.data.is_all_data_prepare()
            if not can_start:
                self.tip_dialog.show_tip(message)
                self.play_state = False
                return

            success, message = self.sim.init_simulate(is_new_start=True, is_change_dispatch_method = self.is_change_dispath_method)
            if not success:
                if message != "":
                    self.tip_dialog.show_tip(message)
                return
            
            self.chart_widget.update_operation_table(self.sim.get_operation_table_data())
            self.new_start = False
    
        self.tool_bar.set_play_button_state(False)
        self.update_content()
        self.graph_widget.hide_vehicle_tip()
        self.graph_widget.can_show_vehicle_tip = False
        self.timer.start(int(BASIC_TIMER_INTERVAL / self.tool_bar.speed_slider.value()))
    
    def pause(self):
        self.graph_widget.can_show_vehicle_tip = True
        self.tool_bar.set_play_button_state(True)
        self.timer.stop()

    def update_content(self):
        success, message = self.sim.forward()
        if self.content_index == 0:
            self.update_chart_content()
        else:
            self.update_graph_content()
            
        if not success:
            self.play_state = False
            self.tip_dialog.show_tip(message)
            self.pause()
            self.sim.init_simulate()

    def restart(self):
        can_start, message = self.sim.data.is_all_data_prepare()
        
        if len(self.sim.cur_dispatch_plan) == 0:
            message = "请先按开始模拟, 以便生成调度方案"
            can_start = False

        if not can_start:
            self.tip_dialog.show_tip(message)
            self.play_state = False
            return

        self.sim.init_simulate()
        self.update_chart_content()
        self.update_graph_content()

        self.play_state = False
        self.pause()
    
    def update_chart_content(self):
        self.chart_widget.update_flight_table(self.sim.data.get_flight_table_data())
        self.chart_widget.update_gate_table(self.sim.data.get_gate_table_data())
        self.chart_widget.update_vehicle_table(self.sim.data.get_vehicle_table_data())
        self.chart_widget.update_cost_chart_view(\
            self.sim.data.cost_data, self.sim.data.cost_sum, self.sim.data.cost_avg, self.sim.data.cost_max)
        self.chart_widget.set_operation_table_top_row(self.sim.cur_operation_index)
        self.chart_widget.clock_widget.update_date_and_time(self.sim.clock.get_date(), self.sim.clock.get_time())

    def update_graph_content(self):
        self.graph_widget.update_graph_content()
        self.graph_widget.clock_widget.update_date_and_time(self.sim.clock.get_date(), self.sim.clock.get_time())
        self.graph_widget.update_operation_list(self.sim.get_operation_table_data(get_all = False, num = 4))
        self.graph_widget.update_flight_list(self.sim.data.get_flight_table_data(num = 4))
        self.graph_widget.update_cost_label(self.sim.data.cost_sum, self.sim.data.cost_avg)

    def open_vehicle_conf(self):        
        self.load_data("打开车辆配置文件", "json", self.sim.data.load_vehicle_date_from_file)

    def open_flight_data_file(self):
        if len(self.sim.data.gate_data) == 0:
            self.tip_dialog.show_tip("请先导入停机位信息文件，以便检查航班信息是否与停机位对应")
            return
        
        loading_func = self.sim.data.load_flight_data_from_file
        success_callback =\
             lambda:(self.chart_widget.update_flight_table(self.sim.data.get_flight_table_data()),\
             self.chart_widget.update_flight_table(self.sim.data.get_flight_table_data()))
        self.load_data("打开航班信息文件", "csv", loading_func, success_callback)
    
    def open_gate_data_file(self):
        loading_func = self.sim.data.load_gate_data_from_file
        success_callback = \
            lambda:self.graph_widget.load_map(
                self.sim.data.lines, 
                self.sim.data.park_data, 
                self.sim.data.gate_data, 
                self.sim.data.vehicle_data)
        self.load_data("打开停机位信息文件", "json", loading_func, success_callback)

    def load_data(self, title:str, type:str, loading_func, success_callback = None):
        file_name = open_file_dialog(title, type)
        if file_name != '':
            self.data_loaded_callback = success_callback
            self.data_loading_thread.loading_func = lambda:loading_func(file_name)

            self.model_tip_dialog.show_tip("数据正在加载中, 请稍后……")
            self.data_loading_thread.start()
    
    def update_widgets_after_loaded_data(self, success:bool, message:str):
        if success:
            if self.data_loaded_callback:
                self.data_loaded_callback()
            self.set_new_start()
            self.model_tip_dialog.hide()
        else:
            self.model_tip_dialog.hide()
            self.tip_dialog.show_tip(message)

    def save_data(self, title:str, type:str, saving_func):
        self.pause()
        file_name = save_file_dialog(title, type)
        if file_name != '':
            self.data_saving_thread.saving_func = lambda:saving_func(file_name)
            self.model_tip_dialog.show_tip("数据正在保存中, 请稍后……")
            self.data_saving_thread.start()

    def save_chart_image(self):
        saving_func = lambda file_name:self.chart_widget.cost_chart.grab().save(file_name, "png")
        self.save_data("保存曲线图", "png", saving_func)
    
    def save_dipath_plan(self):
        if len(self.sim.cur_dispatch_plan) == 0:
            self.tip_dialog.show_tip("没有调度方案数据，请先点击“开始模拟”以生成调度方案")
            return
        
        saving_func = lambda file_name:self.sim.save_dipatch_plan(file_name)
        self.save_data("保存调度方案", "csv", saving_func)
    
    def show_cost_figure(self):
        can_start, message = self.sim.data.is_all_data_prepare()
        if not can_start:
            self.tip_dialog.show_tip(message)
            self.play_state = False
            return
        self.sim.show_cost_chart()

    def show_ga_parms_dialog(self):
        self.ga_parms_dialog.show_parms(*self.sim.get_ga_parms())

    def changed_ga_parms(self, pop_num:int, itera_times:int, cross_rate:float, mutation_rate:float):
        if self.sim.cur_dispatch_method_index == self.sim.ga_dispatch_method_index:
            self.set_new_start()
        else:
            self.sim.dispatch_plans[self.sim.ga_dispatch_method_index] = []
            
        self.sim.set_ga_parms(pop_num, itera_times, cross_rate, mutation_rate)

def run_app():

    #解决Windows系统下任务栏图标不显示的问题
    if platform.system() == "Windows":
        myappid = u'deatrg.AirportDispatchingSimulation'
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)

    app = QApplication(sys.argv)
    app.setWindowIcon(QIcon('icon/plane.svg'))

    main_window = MainWindow()
    with open("style.qss", "r") as f:
        _style = f.read()
        app.setStyleSheet(_style)
    main_window.show()
    
    sys.exit(app.exec())